package util

import generic.{Arg, GenericSignature}
import java.lang.reflect.{WildcardType, TypeVariable, ParameterizedType, Type}
import java.lang.Class

/**
 * <p>
 * Created by IntelliJ IDEA.
 * User: zslajchrt
 * Date: Feb 19, 2011
 * Time: 8:57:29 PM
 */

object GenericAnalysisUtils extends Application {

  //  def getCommonType(types: List[Type]) = {
  //  }

  def getTypeName(t: Type): String = {
    t match {
      case c: Class[_] => c.getName
      case _ => t.toString
    }
  }

  def toString(t: Type): String = {
    t match {
      case c: Class[_] => c.getSimpleName
      case pt: ParameterizedType => {
        val resolvedActualTypes = pt.getActualTypeArguments.map(toString(_)).reduceLeft[String]((acc, typeName) => acc + "," + typeName)
        toString(pt.getRawType) + "[" + resolvedActualTypes + "]"
      }
      // todo
      //      case varRef: TypeVariable[_] => resolveTypeVariable(varRef, genericSubClasses)
      //      case wc: WildcardType => wc
      case _ => t.toString
    }
  }

  def getCommonType(types: Array[Type]): Type = {
    types.foldLeft[Type](classOf[Nothing])((commonType, t) => getCommonType(commonType, t))
  }

  def getCommonType(t1: Type, t2: Type): Type = {
    val rt1 = getRawClassFromType(t1)
    val rt2 = getRawClassFromType(t2)
    val (commonRawType, commonType) = if (rt2 == classOf[Nothing] || rt1.isAssignableFrom(rt2))
      (rt1, t1)
    else if (rt1 == classOf[Nothing] || rt2.isAssignableFrom(rt1))
      (rt2, t2)
    else (classOf[Any], classOf[Any])

    if (commonRawType == classOf[Any])
      commonType
    else {
      if (!t1.isInstanceOf[ParameterizedType] || !t2.isInstanceOf[ParameterizedType]) {
        commonType
      } else {

        val pt1 = t1.asInstanceOf[ParameterizedType]
        val pt2 = t2.asInstanceOf[ParameterizedType]
        require(pt1.getActualTypeArguments.length == pt2.getActualTypeArguments.length, "The number of type arguments must be same")

        val commonArgTypes = pt1.getActualTypeArguments.zip(pt2.getActualTypeArguments).map((a) => {
          val ct: Type = getCommonType(a._1, a._2)
          ct
        })
        new ParameterizedTypeWrapper(null, commonRawType, commonArgTypes)
      }

    }
  }

  def convertToClassName(rawClassSignature: String): String =
    rawClassSignature.substring(1, rawClassSignature.length - 1).replace('/', '.')

  /**
   * Note: the primitive types are not supported ATM
   */
  def parseSignature(signature: String): Type = {

    // Ljava/util/HashMap<TK;TV;>;
    val leftBracket = signature.indexOf('<')
    val rightBracket = if (leftBracket > 0)
      signature.lastIndexOf('>')
    else
      -1

    val (rawTypeSignature, paramSignature) = if (leftBracket > 0) {
      // ("java.util.HashMap", "TK;TV;")
      (signature.substring(0, leftBracket) + ";",
        signature.substring(leftBracket + 1, rightBracket))
    } else
      (signature, null)


    def countDimension(classSignature: String, counter: Int = 0): Int = {
      if (classSignature.startsWith("["))
        countDimension(classSignature.substring(1), counter + 1)
      else
        counter
    }

    val dimCount: Int = countDimension(rawTypeSignature)

    val contextClassLoader: ClassLoader = Thread.currentThread.getContextClassLoader
    val rawClass = if (dimCount > 0) {
      val dimensions = Array.fill(dimCount)(0)
      val componentType = contextClassLoader.loadClass(convertToClassName(rawTypeSignature.substring(dimCount)))
      java.lang.reflect.Array.newInstance(componentType, dimensions: _*).getClass
    } else {
      contextClassLoader.loadClass(convertToClassName(rawTypeSignature))
    }

    if (paramSignature == null)
      rawClass
    else {
      val actualArgs: List[Type] = parseArguments(paramSignature)
      new ParameterizedTypeWrapper(null, rawClass, actualArgs.toArray)
    }

  }

  def parseArguments(argSignature: String): List[Type] = {
    // +Ljava/lang/Number;-Ljava/lang/String;
    // *-Ljava/lang/String;
    var tailSignature: String = null
    val headType = if (argSignature.charAt(0) == '*') {
      tailSignature = argSignature.substring(1)
      parseArgument("*")
    } else {
      val firstSemicolon = argSignature.indexOf(';')
      val headSignature = argSignature.substring(0, firstSemicolon + 1)
      tailSignature = argSignature.substring(firstSemicolon + 1)
      parseArgument(headSignature)
    }

    if (tailSignature == "") {
      List(headType)
    } else {
      headType :: parseArguments(tailSignature)
    }
  }

  def parseArgument(argSignature: String): Type = {
    // +Ljava/lang/Number;
    // -Ljava/lang/Runnable;
    // Ljava/lang/String;
    // *;
    val prefix: Char = argSignature.charAt(0)
    prefix match {
      case '+' => {
        val upperBound = parseSignature(argSignature.substring(1))
        new WildcardTypeWrapper(null, Array(upperBound))
      }
      case '-' => {
        val lowerBound = parseSignature(argSignature.substring(1))
        new WildcardTypeWrapper(Array(lowerBound), Array(classOf[Object]))
      }
      case '*' => {
        new WildcardTypeWrapper(null, Array(classOf[Object]))
      }
      case _ => {
        parseSignature(argSignature)
      }
    }

  }

  def getSignature(t: Type, inclusion: () => String = () => ""): String = {
    t match {
      case c: Class[_] => "L" + c.getName.replace(".", "/") + inclusion() + ";"
      case pt: ParameterizedType => {
        getSignature(pt.getRawType, () => {
          pt.getActualTypeArguments.foldLeft("<")((s, actual) => {
            s + getSignature(actual)
          }) + ">"
        })
      }
      case wc: WildcardType => {
        if (wc.getUpperBounds.length == 1 && wc.getUpperBounds()(0) == classOf[Object] &&
          wc.getLowerBounds.isEmpty) {
          // '?' wildcard
          "*"
        } else {
          val uppers = wc.getUpperBounds.foldLeft("")((s, upper) => {
            if (upper != classOf[Object])
              s + "+" + getSignature(upper)
            else s
          })
          val lowers = wc.getLowerBounds.foldLeft("")((s, lower) => {
            s + "-" + getSignature(lower)
          })
          uppers + lowers
        }
      }
      case _ => error("unsupported type " + t)
    }
  }

  def getParameterTypes(t: Type): Array[Type] = {
    t match {
      case pt: ParameterizedType => {
        pt.getActualTypeArguments()
      }
      case _ => error("unsupported type " + t)
    }
  }

  def getParameterType(t: Type, argIndex: Int): Type = {
    t match {
      case pt: ParameterizedType => {
        require(pt.getActualTypeArguments().length > argIndex)
        pt.getActualTypeArguments()(argIndex)
      }
      case _ => error("unsupported type " + t)
    }
  }

  def getRawClassFromType(t: Type): Class[_] = {
    t match {
      case c: Class[_] => c
      case pt: ParameterizedType => getRawClassFromType(pt.getRawType)
      case wc: WildcardType => {
        val upper = wc.getUpperBounds
        if (upper.length == 1)
          getRawClassFromType(upper(0))
        else
          error(t + " wildcard type must have only one upper bound type")
      }
      case _ => error("unsupported type " + t)
    }
  }

  def resolveTypeVariable(varRef: TypeVariable[_], genericSubClasses: List[Type]): Type = {
    if (genericSubClasses.isEmpty)
      error("Type variable " + varRef + " cannot be resolved")

    val contextClass = genericSubClasses.head.asInstanceOf[ParameterizedType].getRawType.asInstanceOf[Class[_]]

    val indexOfVarDecl = contextClass.getTypeParameters.indexWhere(_.getName == varRef.getName)
    if (indexOfVarDecl < 0)
      error("Type variable " + varRef.getName + " not found")
    else {
      genericSubClasses.head match {
        case pt: ParameterizedType => {
          val subClassActualArg = pt.getActualTypeArguments()(indexOfVarDecl)
          resolveActualArgument(subClassActualArg, genericSubClasses.tail)
        }
        case _ => error("Parameterized type expected")
      }
    }
  }

  def resolveActualArgument(actualType: Type, genericSubClasses: List[Type]): Type = {
    actualType match {
      case c: Class[_] => c
      case varRef: TypeVariable[_] => resolveTypeVariable(varRef, genericSubClasses)
      case pt: ParameterizedType => {
        val resolvedActualTypes = pt.getActualTypeArguments.map(resolveActualArgument(_, genericSubClasses))
        new ParameterizedTypeWrapper(pt.getOwnerType, pt.getRawType, resolvedActualTypes)
      }
      case wc: WildcardType => wc
      case _ => error("unsupported actual type argument:" + actualType)
    }
  }

  def findActualTypeParameters(cls: Class[_], ancestor: Class[_], genericSubClasses: List[Type] = Nil): Option[Array[Type]] = {

    def resolveActualArguments(genericType: Type): Array[Type] = {
      genericType match {
        case cls: Class[_] => {
          // no parameters
          Array.empty
        }
        case paramType: ParameterizedType => {
          paramType.getActualTypeArguments.map(resolveActualArgument(_, genericSubClasses))
        }
        case _ => error("unsupported type:" + genericType)
      }
    }

    if (ancestor == classOf[Object]) {
      Some(Array.empty)
    } else if (ancestor == cls) {
      // cls = String, ancestor = String, genericSubClasses= {}
      // cls = List, ancestor = List, genericSubClasses = {List[String]}
      //
      // The ancestor and cls are the same classes. The actual arguments can be pulled out from the head of
      // the genericSubclasses list if the raw type of the head type is the same as the ancestor (and )
      //
      if (!genericSubClasses.isEmpty)
        genericSubClasses.head match {
          case ptSubCls: ParameterizedType => {
            if (ptSubCls.getRawType == ancestor)
              Some(ptSubCls.getActualTypeArguments)
            else
              Some(Array.empty)
          }
        }
      else Some(Array.empty)

    } else if (ancestor.isInterface) {
      var result: Option[Array[Type]] = null

      cls.getInterfaces.zip(cls.getGenericInterfaces).find(ancestorPair => {
        if (ancestorPair._1 == ancestor) {
          result = Some(resolveActualArguments(ancestorPair._2))
          true
        } else {
          // recursion - delve into the current interface's interfaces
          val superIntf: Class[_] = ancestorPair._1
          if (superIntf != null)
            findActualTypeParameters(superIntf /*super-interface*/ , ancestor,
              ancestorPair._2 :: genericSubClasses) match {
              case None => {
                result = None
                false
              }
              case Some(res) => {
                result = Some(res)
                true
              }
            }
          else {
            result = None
            false
          }

        }
      }) match {
        case Some(_) => result
        case None => {
          if (cls.getSuperclass != null)
          // traverse the super-class
            findActualTypeParameters(cls.getSuperclass, ancestor, cls.getGenericSuperclass :: genericSubClasses)
          else None
          //          if (ancestor == cls) {
          //            // The ancestor was not found among ancestors of cls, however, they are the same classes.
          //            //
          //            if (!genericSubClasses.isEmpty)
          //              genericSubClasses.head match {
          //                case ptSubCls: ParameterizedType => {
          //                  if (ptSubCls.getRawType == ancestor)
          //                    Some(ptSubCls.getActualTypeArguments)
          //                  else
          //                    None
          //                }
          //              }
          //            else None
          //          } else None
        }
      }

    } else {
      // the anchor ancestor is a class
      if (cls.getSuperclass == ancestor) {
        Some(resolveActualArguments(cls.getGenericSuperclass))
      } else if (cls.getSuperclass != null)
        findActualTypeParameters(cls.getSuperclass, ancestor, cls.getGenericSuperclass :: genericSubClasses)
      else None
    }
  }

  def applyScalaSignature(t: Type): Type = {
    t match {
      case pt: ParameterizedType => {
        val rawClass = getRawClassFromType(pt)
        val genSign: GenericSignature = rawClass.getAnnotation(classOf[GenericSignature])
        if (genSign == null)
          t
        else {
          val newActualArgs = genSign.value.zip(pt.getActualTypeArguments).map(pair => {
            val arg = pair._1
            val at = pair._2

            arg match {
              case Arg.INVARIANT => at
              case Arg.COVARIANT => new WildcardTypeWrapper(null, Array(at))
              case Arg.CONTRAVARIANT => new WildcardTypeWrapper(Array(at), Array(classOf[Object]))
            }
          })
          new ParameterizedTypeWrapper(pt.getOwnerType, pt.getRawType, newActualArgs)
        }

      }
      case _ => t
    }
  }

  def isCompatible(source: Type, target: Type, compareAsIdentity: Boolean = false): Boolean = {
    //val sourceType = applyScalaSignature(source)
    val sourceType = source
    val targetType = applyScalaSignature(target)

    if (sourceType == classOf[Nothing])
      true
    else if (targetType == classOf[Any])
      true
    else
      sourceType match {

        case wcSource: WildcardType => {
          // <? extends S> -> T  => T -> S
          val upperTest = wcSource.getUpperBounds.forall(upperType => {
            isCompatible_(targetType, upperType, false)
          })

          // <? super S> -> T  => S -> T
          val lowerTest = wcSource.getLowerBounds.forall(lowerType => {
            isCompatible_(lowerType, targetType, false)
          })

          upperTest && lowerTest
        }

        case _ => {

          targetType match {

            case wcTarget: WildcardType => {
              // S -> <? extends T> => S -> T
              val upperTest = wcTarget.getUpperBounds.forall(upperType => {
                isCompatible_(sourceType, upperType, false)
              })

              // S -> <? super T> => T -> S
              val lowerTest = if (wcTarget.getLowerBounds != null) {
                wcTarget.getLowerBounds.forall(lowerType => {
                  isCompatible_(lowerType, sourceType, false)
                })
              } else true

              upperTest && lowerTest
            }

            case _ => isCompatible_(sourceType, targetType, compareAsIdentity)
          }
        }
      }
  }

  private def isCompatible_(sourceType: Type, targetType: Type, compareAsIdentity: Boolean): Boolean = {
    sourceType match {
      case cSource: Class[_] => {

        targetType match {
          case cTarget: Class[_] => {
            // ex. Runnable <- MyRunnable
            if (compareAsIdentity)
              cTarget == cSource
            else
              cTarget.isAssignableFrom(cSource)
          }

          case ptTarget: ParameterizedType => {
            // ex. List[String] <- MyListOfString
            ptTarget.getRawType match {
              case cTarget: Class[_] => {
                findActualTypeParameters(cSource, cTarget) match {
                  case None => false
                  case Some(actualArgs) => {
                    compareActualArguments(actualArgs, ptTarget.getActualTypeArguments, true)
                  }
                }
              }
              case _ => error("unsupported")
            }
          }
        }

      }

      case ptSource: ParameterizedType => {

        targetType match {
          case cTarget: Class[_] => {
            // ex. Runnable <- MyListOfString[Int]
            ptSource.getRawType match {
              case cSource: Class[_] => {
                findActualTypeParameters(cSource, cTarget) match {
                  case None => false
                  case Some(actualArgs) => {
                    // the actual argument of the source type does not relate to the target type
                    actualArgs.length == 0
                  }
                }
              }
              case _ => error("unsupported")
            }
          }

          case ptTarget: ParameterizedType => {
            // ex. List[String] <- MyList[String]
            // ex. List[String] <- MyStringList[Runnable]
            ptTarget.getRawType match {
              case cTarget: Class[_] => {

                ptSource.getRawType match {
                  case cSource: Class[_] => {

                    if (!cTarget.isAssignableFrom(cSource))
                      false
                    else {
                      findActualTypeParameters(cSource, cTarget, List(ptSource)) match {
                        case None => false
                        case Some(actualArgs) => {
                          compareActualArguments(actualArgs, ptTarget.getActualTypeArguments, true)
                        }
                      }
                    }
                  }
                  case _ => error("unsupported")
                }

              }
              case _ => error("unsupported")
            }
          }
        }

      }

      case _ => error("unsupported")
    }
  }

  def compareActualArguments(sourceActualArgs: Array[Type], targetActualArgs: Array[Type], compareAsIdentity: Boolean): Boolean = {
    if (sourceActualArgs.length == targetActualArgs.length) {
      // only target args may contain wild-cards
      sourceActualArgs.zip(targetActualArgs).find(pair => !isCompatible(pair._1, pair._2, compareAsIdentity)) == None
    } else false
  }

}

class ParameterizedTypeWrapper(ownerType: Type, rawType: Type, actualTypeArgs: Array[Type]) extends ParameterizedType {
  def getOwnerType = ownerType

  def getRawType = rawType

  def getActualTypeArguments = actualTypeArgs

  override def toString = GenericAnalysisUtils.toString(this)
}

class WildcardTypeWrapper(lowerBounds: Array[Type], upperBounds: Array[Type]) extends WildcardType {
  def getLowerBounds = lowerBounds

  def getUpperBounds = upperBounds
}

